from threading import local
from enfold.xapian.store import Store

def get_local(ob, name, factory):
    got = getattr(ob, name, None)
    if got is None:
        setattr(ob, name, factory())
        return getattr(ob, name)
    return got

DEL, ADD = range(1, 3)

class QueueStore(Store):
    """A thread-safe queue store
    """

    def __init__(self, *args, **kw):
        Store.__init__(self, *args, **kw)
        self.add_hook = kw.get('add_hook')
        self.remove_hook = kw.get('remove_hook')
        self._reset()

    def add(self, data):
        uid = data.get('uid', None)
        if not uid:
            msg = 'You must pass in the uid of the object to be indexed.'
            raise ValueError, msg

        if self.add_hook is not None:
            self.add_hook()

        uid = data.get('uid', None)
        info = get_local(self.queue_local, 'info', dict)
        queue = get_local(self.queue_local, 'queue', list)
        
        info[uid] = data # We stomp over previous values here.
        queue.append((uid, ADD))

    def remove(self, uid):
        if not uid:
            msg = 'You must pass in the uid of the object to be indexed.'
            raise ValueError, msg

        if self.remove_hook is not None:
            self.remove_hook()

        queue = get_local(self.queue_local, 'queue', list)
        queue.append((uid, DEL))

    def _reset(self):
        self.queue_local = local()
        
    def reset(self, *args, **kw):
        self._reset()
        Store.reset(self, *args, **kw)

    def processQueue(self):
        queue = get_local(self.queue_local, 'queue', list)
        info = get_local(self.queue_local, 'info', dict)

        # Weed out operations that cancel one another.
        #
        # Rules:
        #
        # 1. A remove after an add for the same uid means we
        #    don't even try to add it.
        # 2. For an add after a remove, we skip the remove since
        #    Xapian will 'replace' the document under the hood.
        # 3. For one or more adds, ignore all but the last one.
        #
        # With some logic, we can reduce those rules to just one:
        #
        #  "Whatever the last operation is, just use that
        #   and ignore everything else."

        ops = {}
        for idx, (uid, op) in enumerate(queue):
            ops[(uid, op)] = (idx, uid, op)
            
        # Now process the queue.
        for idx, uid, op in sorted(ops.itervalues()):
            if op in (DEL,):
                Store.remove(self, uid)
            elif op in (ADD,):
                Store.add(self, info.get(uid))

        self.clearQueue()

    def clearQueue(self):
        queue = get_local(self.queue_local, 'queue', list)
        del queue[:]
        
        info = get_local(self.queue_local, 'info', dict)
        info.clear()

    def getQueueState(self):
        info = get_local(self.queue_local, 'info', dict)
        queue = get_local(self.queue_local, 'queue', list)
        
        return info.copy(), queue[:]

    def setQueueState(self, state):
        info, queue = state
        self.queue_local.info = info
        self.queue_local.queue = queue
